import os
import subprocess
import sys
from typing import List, Optional, Dict
import tkinter as tk
from tkinter import ttk, filedialog, messagebox


class TSMergerGUI:
    def __init__(self, root):
        self.root = root
        self.root.title("TS视频合并工具 - 支持子文件夹查找")
        self.root.geometry("1000x700")
        self.root.resizable(True, True)

        # 全局变量
        self.target_root_folder: Optional[str] = None  # 目标根文件夹（用于递归查找）
        self.found_ts_folders: Dict[str, List[str]] = {}  # 找到的TS文件夹：{文件夹路径: [sorted_ts_files]}
        self.selected_ts_folder: Optional[str] = None  # 当前选择的TS文件夹
        self.default_save_path = r"E:\视频录制"  # 默认保存路径
        self.save_path: Optional[str] = self.default_save_path
        self.output_filename = "merged_output.mp4"

        # 初始化默认保存路径
        self._init_default_save_path()

        # 构建界面
        self._setup_ui()

    def _init_default_save_path(self):
        """初始化默认保存路径（不存在则创建）"""
        try:
            if not os.path.exists(self.default_save_path):
                os.makedirs(self.default_save_path)
                print(f"已创建默认保存路径：{self.default_save_path}")
        except Exception as e:
            self.default_save_path = os.path.join(os.path.expanduser("~"), "Desktop")
            self.save_path = self.default_save_path
            messagebox.showwarning(
                "路径创建失败",
                f"无法创建默认保存路径「E:\\视频录制」\n"
                f"错误原因：{str(e)}\n"
                f"将默认保存路径改为桌面：{self.default_save_path}"
            )

    def _setup_ui(self):
        """重构界面，添加子文件夹查找相关控件"""
        # 1. 顶部控制区（新增递归查找选项）
        control_frame = ttk.Frame(self.root, padding="10")
        control_frame.pack(fill=tk.X, expand=False, anchor=tk.N)

        # 第一行：选择根文件夹 + 递归查找按钮
        self.select_root_btn = ttk.Button(
            control_frame,
            text="选择目标根文件夹",
            command=self.select_root_folder
        )
        self.select_root_btn.grid(row=0, column=0, padx=5, pady=5, sticky=tk.W)

        self.root_folder_label = ttk.Label(
            control_frame,
            text="未选择根文件夹",
            width=30,
            wraplength=220
        )
        self.root_folder_label.grid(row=0, column=1, padx=5, pady=5, sticky=tk.W)

        self.scan_btn = ttk.Button(
            control_frame,
            text="扫描所有子文件夹TS文件",
            command=self.scan_ts_folders,
            state=tk.DISABLED
        )
        self.scan_btn.grid(row=0, column=2, padx=5, pady=5, sticky=tk.W)

        # 第二行：TS文件夹选择 + 保存路径选择
        ttk.Label(control_frame, text="找到的TS文件夹:").grid(row=1, column=0, padx=5, pady=5, sticky=tk.E)
        self.ts_folder_combobox = ttk.Combobox(control_frame, width=45, state="readonly")
        self.ts_folder_combobox.grid(row=1, column=1, padx=5, pady=5, sticky=tk.W + tk.E)
        self.ts_folder_combobox.bind("<<ComboboxSelected>>", self.on_ts_folder_selected)

        self.select_save_btn = ttk.Button(
            control_frame,
            text="选择保存路径",
            command=self.select_save_path,
            state=tk.NORMAL
        )
        self.select_save_btn.grid(row=1, column=2, padx=5, pady=5, sticky=tk.W)

        self.save_label = ttk.Label(
            control_frame,
            text=self.save_path if len(self.save_path) <= 30 else f"...{self.save_path[-27:]}",
            width=30,
            wraplength=220
        )
        self.save_label.grid(row=1, column=3, padx=5, pady=5, sticky=tk.W)

        # 第三行：输出文件名 + 合并按钮 + 批量合并按钮
        ttk.Label(control_frame, text="输出文件名:").grid(row=2, column=0, padx=5, pady=5, sticky=tk.E)
        self.output_entry = ttk.Entry(control_frame, width=35)
        self.output_entry.insert(0, self.output_filename)
        self.output_entry.grid(row=2, column=1, padx=5, pady=5, sticky=tk.W + tk.E)

        self.merge_btn = ttk.Button(
            control_frame,
            text="合并当前选择文件夹",
            command=self.start_merge_single,
            state=tk.DISABLED,
            style="Accent.TButton"
        )
        self.merge_btn.grid(row=2, column=2, padx=5, pady=5, sticky=tk.W)

        self.batch_merge_btn = ttk.Button(
            control_frame,
            text="批量合并所有TS文件夹",
            command=self.start_batch_merge,
            state=tk.DISABLED,
            style="Warning.TButton"
        )
        self.batch_merge_btn.grid(row=2, column=3, padx=5, pady=5, sticky=tk.E)

        # 2. 中间文件列表区
        list_frame = ttk.Frame(self.root, padding="10")
        list_frame.pack(fill=tk.BOTH, expand=True, pady=(0, 10))

        # 列表标题
        title_frame = ttk.Frame(list_frame)
        title_frame.pack(fill=tk.X, expand=False, anchor=tk.W)
        ttk.Label(title_frame, text="当前文件夹下的TS文件（按数字顺序）:", font=("Arial", 11, "bold")).pack(side=tk.LEFT)
        self.file_count_label = ttk.Label(title_frame, text="(0个文件)")
        self.file_count_label.pack(side=tk.LEFT, padx=10)
        self.folder_depth_label = ttk.Label(title_frame, text="")
        self.folder_depth_label.pack(side=tk.LEFT, padx=10)

        # 滚动条 + 列表框
        scrollbar = ttk.Scrollbar(list_frame)
        scrollbar.pack(side=tk.RIGHT, fill=tk.Y)

        self.file_listbox = tk.Listbox(
            list_frame,
            yscrollcommand=scrollbar.set,
            font=("Arial", 10),
            selectbackground="#e0e0e0",
            bg="white"
        )
        self.file_listbox.pack(fill=tk.BOTH, expand=True, padx=(0, 5))
        scrollbar.config(command=self.file_listbox.yview)

        # 3. 底部状态区
        status_frame = ttk.Frame(self.root, padding="10")
        status_frame.pack(fill=tk.X, expand=False, anchor=tk.S)

        self.status_label = ttk.Label(
            status_frame,
            text=f"就绪 - 请选择目标根文件夹，支持递归查找子文件夹（默认保存路径：{self.default_save_path}）",
            font=("Arial", 10)
        )
        self.status_label.pack(anchor=tk.W, pady=5)

        self.progress_var = tk.DoubleVar()
        self.progress_bar = ttk.Progressbar(
            status_frame,
            variable=self.progress_var,
            maximum=100,
            mode="determinate"
        )
        self.progress_bar.pack(fill=tk.X, padx=5, pady=5, expand=True)
        self.progress_bar.pack_forget()

        # 样式配置
        self.root.style = ttk.Style()
        self.root.style.configure("Accent.TButton", font=("Arial", 10, "bold"), foreground="white",
                                  background="#2196F3")
        self.root.style.configure("Warning.TButton", font=("Arial", 10, "bold"), foreground="white",
                                  background="#FF5722")

    def _get_sorted_ts_files(self, folder_path: str) -> List[str]:
        """查找单个文件夹下的TS文件并排序"""
        ts_files = []
        for filename in os.listdir(folder_path):
            file_path = os.path.join(folder_path, filename)
            if os.path.isfile(file_path) and filename.endswith(".ts"):
                try:
                    num_part = ''.join(filter(str.isdigit, filename))
                    if not num_part:
                        continue
                    file_num = int(num_part)
                    ts_files.append((file_num, os.path.abspath(file_path)))
                except Exception:
                    continue

        ts_files.sort(key=lambda x: x[0])
        return [path for _, path in ts_files]

    def scan_ts_folders(self):
        """递归扫描根文件夹下所有子文件夹，查找包含TS文件的文件夹"""
        if not self.target_root_folder:
            messagebox.showwarning("警告", "请先选择目标根文件夹！")
            return

        self.status_label.config(text=f"正在递归扫描文件夹：{self.target_root_folder}...")
        self.root.update_idletasks()

        # 清空之前的查找结果
        self.found_ts_folders.clear()
        self.ts_folder_combobox["values"] = []
        self.file_listbox.delete(0, tk.END)
        self.file_count_label.config(text="(0个文件)")
        self.folder_depth_label.config(text="")

        try:
            # 递归遍历所有子文件夹
            for root, dirs, files in os.walk(self.target_root_folder):
                # 查找当前文件夹下的TS文件
                ts_files = self._get_sorted_ts_files(root)
                if ts_files:
                    # 保存包含TS文件的文件夹
                    self.found_ts_folders[root] = ts_files

            if not self.found_ts_folders:
                messagebox.showwarning("警告", "未在目标文件夹及子文件夹中找到任何TS文件！")
                self.status_label.config(text=f"扫描完成 - 未找到TS文件（默认保存路径：{self.default_save_path}）")
                self.merge_btn.config(state=tk.DISABLED)
                self.batch_merge_btn.config(state=tk.DISABLED)
            else:
                # 更新下拉框选项（显示相对路径，更友好）
                combobox_values = []
                for folder_path in self.found_ts_folders.keys():
                    # 计算相对路径，显示更简洁
                    rel_path = os.path.relpath(folder_path, self.target_root_folder)
                    display_text = f"{rel_path}（{len(self.found_ts_folders[folder_path])}个文件）"
                    combobox_values.append((folder_path, display_text))

                # 按文件夹深度排序
                combobox_values.sort(key=lambda x: x[0].count(os.sep))
                self.ts_folder_combobox["values"] = [v[1] for v in combobox_values]
                self.ts_folder_combobox.current(0)  # 默认选择第一个

                # 启用按钮
                self.merge_btn.config(state=tk.NORMAL)
                self.batch_merge_btn.config(state=tk.NORMAL if len(self.found_ts_folders) > 1 else tk.DISABLED)

                # 更新状态提示
                self.status_label.config(
                    text=f"扫描完成 - 找到 {len(self.found_ts_folders)} 个包含TS文件的文件夹（当前保存路径：{self.save_path}）"
                )

                # 自动加载第一个文件夹的文件列表
                self.selected_ts_folder = combobox_values[0][0]
                self._update_file_list(self.selected_ts_folder)

        except Exception as e:
            messagebox.showerror("错误", f"扫描文件夹失败：{str(e)}")
            self.status_label.config(text=f"扫描失败（默认保存路径：{self.default_save_path}）")

    def _update_file_list(self, folder_path: str):
        """更新当前选择文件夹的TS文件列表"""
        ts_files = self.found_ts_folders.get(folder_path, [])
        self.file_listbox.delete(0, tk.END)

        for i, file_path in enumerate(ts_files, 1):
            filename = os.path.basename(file_path)
            self.file_listbox.insert(tk.END, f"{i:03d}. {filename}")

        self.file_count_label.config(text=f"({len(ts_files)}个文件)")

        # 显示文件夹深度
        depth = folder_path.count(os.sep) - self.target_root_folder.count(os.sep)
        self.folder_depth_label.config(text=f"（深度：{depth}级子文件夹）")

    def select_root_folder(self):
        """选择目标根文件夹（用于递归查找）"""
        folder = filedialog.askdirectory(title="选择目标根文件夹（会递归查找子文件夹）")
        if not folder:
            return

        self.target_root_folder = folder
        display_path = folder if len(folder) <= 30 else f"...{folder[-27:]}"
        self.root_folder_label.config(text=display_path)

        # 启用扫描按钮
        self.scan_btn.config(state=tk.NORMAL)
        self.status_label.config(text=f"已选择根文件夹：{display_path}，请点击扫描按钮查找TS文件")

    def on_ts_folder_selected(self, event):
        """选择下拉框中的TS文件夹"""
        selected_text = self.ts_folder_combobox.get()
        if not selected_text:
            return

        # 找到对应的文件夹路径
        for folder_path, display_text in zip(self.found_ts_folders.keys(), self.ts_folder_combobox["values"]):
            if display_text == selected_text:
                self.selected_ts_folder = folder_path
                self._update_file_list(folder_path)
                break

    def select_save_path(self):
        """选择自定义保存路径"""
        save_folder = filedialog.askdirectory(title="选择合并后MP4文件的保存路径")
        if not save_folder:
            return

        self.save_path = save_folder
        display_save = self.save_path if len(self.save_path) <= 30 else f"...{self.save_path[-27:]}"
        self.save_label.config(text=display_save)

        # 更新状态提示
        if self.found_ts_folders:
            self.status_label.config(
                text=f"保存路径已更新：{display_save}（找到 {len(self.found_ts_folders)} 个TS文件夹）"
            )
        else:
            self.status_label.config(text=f"保存路径已更新：{display_save}（默认路径：{self.default_save_path}）")

    def _create_temp_file_list(self, ts_files: List[str], temp_list_path: str) -> None:
        """创建TS文件列表（供ffmpeg使用）"""
        with open(temp_list_path, "w", encoding="utf-8") as f:
            for file in ts_files:
                file_path = file.replace("\\", "/")
                f.write(f"file '{file_path}'\n")

    def start_merge_single(self):
        """合并当前选择的单个TS文件夹"""
        if not self.selected_ts_folder or not self.found_ts_folders:
            messagebox.showwarning("警告", "请先选择包含TS文件的文件夹！")
            return

        if not self.save_path:
            messagebox.showwarning("警告", "请选择保存路径！")
            return

        # 获取输出文件名
        output_name = self.output_entry.get().strip()
        if not output_name:
            messagebox.showwarning("警告", "输出文件名不能为空！")
            return

        # 确保输出格式是MP4
        if not output_name.lower().endswith(".mp4"):
            output_name += ".mp4"
            self.output_entry.delete(0, tk.END)
            self.output_entry.insert(0, output_name)

        # 构建完整输出路径
        output_path = os.path.join(self.save_path, output_name)

        # 检查输出文件是否已存在
        if os.path.exists(output_path):
            if not messagebox.askyesno("确认覆盖", f"文件「{output_name}」已存在，是否覆盖？"):
                return

        # 获取当前选择文件夹的TS文件
        ts_files = self.found_ts_folders[self.selected_ts_folder]

        # 准备合并
        self._set_controls_state(tk.DISABLED)
        self.status_label.config(text=f"正在合并：{os.path.basename(self.selected_ts_folder)}...")
        self.progress_bar.pack(fill=tk.X, padx=5, pady=5, expand=True)
        self.progress_var.set(10)
        self.root.update_idletasks()

        try:
            # 创建临时文件列表
            temp_list_path = os.path.join(self.save_path, "temp_ts_list.txt")
            self._create_temp_file_list(ts_files, temp_list_path)
            self.progress_var.set(30)
            self.root.update_idletasks()

            # 构建ffmpeg命令
            self.status_label.config(text="正在合并视频...（无损合并，速度很快）")
            self.root.update_idletasks()

            command = [
                "ffmpeg",
                "-f", "concat",
                "-safe", "0",
                "-i", temp_list_path,
                "-c", "copy",
                "-bsf:a", "aac_adtstoasc",
                "-y",
                output_path
            ]

            # 执行合并命令
            self.progress_var.set(50)
            result = subprocess.run(
                command,
                stdout=subprocess.PIPE,
                stderr=subprocess.STDOUT,
                text=True,
                encoding="utf-8",
                timeout=None
            )

            if result.returncode == 0:
                self.progress_var.set(100)
                self.status_label.config(text="合并成功！")
                messagebox.showinfo(
                    "合并成功",
                    f"✅ 视频合并完成！\n\n源文件夹：\n{self.selected_ts_folder}\n\n输出文件：\n{output_name}\n\n保存位置：\n{self.save_path}"
                )

                # 询问是否打开保存文件夹
                if messagebox.askyesno("打开文件夹", "是否立即打开保存文件所在文件夹？"):
                    self._open_folder(self.save_path)
            else:
                raise Exception(f"ffmpeg执行失败：\n{result.stdout[:500]}")

        except Exception as e:
            self.status_label.config(text="合并失败！")
            messagebox.showerror("合并失败", f"❌ 合并过程中出现错误：\n{str(e)}")
            self.progress_var.set(0)
        finally:
            # 清理临时文件
            temp_list_path = os.path.join(self.save_path, "temp_ts_list.txt")
            if os.path.exists(temp_list_path):
                try:
                    os.remove(temp_list_path)
                except:
                    pass

            # 恢复控件状态
            self._set_controls_state(tk.NORMAL)
            self.progress_bar.pack_forget()
            self.root.update_idletasks()

    def start_batch_merge(self):
        """批量合并所有找到的TS文件夹"""
        if not self.found_ts_folders or len(self.found_ts_folders) <= 1:
            messagebox.showwarning("警告", "没有足够的TS文件夹可批量合并！")
            return

        if not self.save_path:
            messagebox.showwarning("警告", "请选择保存路径！")
            return

        # 确认批量合并
        confirm = messagebox.askyesno(
            "确认批量合并",
            f"即将批量合并 {len(self.found_ts_folders)} 个TS文件夹！\n"
            f"每个文件夹的合并文件会以文件夹名为前缀命名\n"
            f"保存位置：{self.save_path}\n"
            f"是否继续？"
        )
        if not confirm:
            return

        # 准备批量合并
        self._set_controls_state(tk.DISABLED)
        self.status_label.config(text=f"开始批量合并 {len(self.found_ts_folders)} 个文件夹...")
        self.progress_bar.pack(fill=tk.X, padx=5, pady=5, expand=True)
        self.progress_var.set(0)
        self.root.update_idletasks()

        success_count = 0
        fail_folders = []
        total_folders = len(self.found_ts_folders)

        try:
            for idx, (folder_path, ts_files) in enumerate(self.found_ts_folders.items(), 1):
                folder_name = os.path.basename(folder_path)
                self.status_label.config(text=f"正在合并 ({idx}/{total_folders})：{folder_name}")
                self.progress_var.set((idx / total_folders) * 80)  # 留20%给最终完成
                self.root.update_idletasks()

                # 输出文件名：文件夹名 + .mp4（避免重复）
                output_name = f"{folder_name}.mp4"
                output_path = os.path.join(self.save_path, output_name)

                # 检查是否已存在
                if os.path.exists(output_path):
                    output_name = f"{folder_name}_{idx}.mp4"
                    output_path = os.path.join(self.save_path, output_name)

                # 创建临时文件列表
                temp_list_path = os.path.join(self.save_path, f"temp_ts_list_{idx}.txt")
                self._create_temp_file_list(ts_files, temp_list_path)

                # 执行合并
                command = [
                    "ffmpeg",
                    "-f", "concat",
                    "-safe", "0",
                    "-i", temp_list_path,
                    "-c", "copy",
                    "-bsf:a", "aac_adtstoasc",
                    "-y",
                    output_path
                ]

                result = subprocess.run(
                    command,
                    stdout=subprocess.PIPE,
                    stderr=subprocess.STDOUT,
                    text=True,
                    encoding="utf-8",
                    timeout=None
                )

                # 清理当前临时文件
                if os.path.exists(temp_list_path):
                    os.remove(temp_list_path)

                if result.returncode == 0:
                    success_count += 1
                else:
                    fail_folders.append(f"{folder_name}（错误：{result.stdout[:100]}）")

            # 批量合并完成
            self.progress_var.set(100)
            self.status_label.config(text=f"批量合并完成！")

            # 显示结果汇总
            result_text = f"✅ 批量合并结果：\n"
            result_text += f"总文件夹数：{total_folders}\n"
            result_text += f"成功合并：{success_count}\n"
            result_text += f"合并失败：{len(fail_folders)}\n"

            if fail_folders:
                result_text += f"\n失败文件夹：\n" + "\n".join(fail_folders[:5])  # 最多显示5个失败项
                if len(fail_folders) > 5:
                    result_text += f"\n... 还有 {len(fail_folders) - 5} 个文件夹合并失败"

            messagebox.showinfo("批量合并完成", result_text)

            # 询问是否打开保存文件夹
            if messagebox.askyesno("打开文件夹", "是否立即打开保存文件所在文件夹？"):
                self._open_folder(self.save_path)

        except Exception as e:
            self.status_label.config(text="批量合并失败！")
            messagebox.showerror("批量合并失败", f"❌ 批量合并过程中出现错误：\n{str(e)}")
            self.progress_var.set(0)
        finally:
            # 清理所有临时文件
            for idx in range(1, total_folders + 1):
                temp_list_path = os.path.join(self.save_path, f"temp_ts_list_{idx}.txt")
                if os.path.exists(temp_list_path):
                    try:
                        os.remove(temp_list_path)
                    except:
                        pass

            # 恢复控件状态
            self._set_controls_state(tk.NORMAL)
            self.progress_bar.pack_forget()
            self.root.update_idletasks()

    def _set_controls_state(self, state: str):
        """设置所有控件的状态（启用/禁用）"""
        self.select_root_btn.config(state=state)
        self.scan_btn.config(state=state)
        self.ts_folder_combobox.config(state="normal" if state == tk.NORMAL else "disabled")
        self.select_save_btn.config(state=state)
        self.output_entry.config(state=state)
        self.merge_btn.config(state=state)
        self.batch_merge_btn.config(state=state if len(self.found_ts_folders) > 1 else tk.DISABLED)

    def _open_folder(self, folder_path: str):
        """跨平台打开文件夹"""
        try:
            if sys.platform == "win32":
                os.startfile(folder_path)
            elif sys.platform == "darwin":
                subprocess.run(["open", folder_path])
            else:
                subprocess.run(["xdg-open", folder_path])
        except Exception as e:
            messagebox.showwarning("警告", f"无法打开文件夹：{str(e)}")


def main():
    # 检查ffmpeg是否安装
    try:
        subprocess.run(
            ["ffmpeg", "-version"],
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
            check=True,
            timeout=5
        )
    except (subprocess.CalledProcessError, FileNotFoundError, TimeoutError):
        messagebox.showerror(
            "错误 - 未找到ffmpeg",
            "❌ 无法找到ffmpeg工具！\n\n请先安装ffmpeg并配置环境变量：\n"
            "• Windows：下载后将bin目录添加到系统环境变量\n"
            "• Mac：打开终端执行 brew install ffmpeg\n"
            "• Linux：打开终端执行 sudo apt install ffmpeg"
        )
        sys.exit(1)

    # 启动GUI
    root = tk.Tk()
    app = TSMergerGUI(root)
    root.mainloop()


if __name__ == "__main__":
    main()